/*
 * CompositeListUnion.java March 2011
 *
 * Copyright (C) 2011, Niall Gallagher <niallg@users.sf.net>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package wx.xml.simpleframework.xml.core;

import java.util.Collection;
import java.util.Collections;

import wx.xml.simpleframework.xml.strategy.Type;
import wx.xml.simpleframework.xml.stream.InputNode;
import wx.xml.simpleframework.xml.stream.OutputNode;
import wx.xml.simpleframework.xml.stream.Style;

/**
 * The <code>CompositeListUnion</code> object is used to act as a
 * mediator for multiple converters associated with a particular union
 * group. This will basically determine which <code>Converter</code>
 * should be delegated to based on either the XML element name being read
 * or the type of the instance object being written. Selection of the
 * converter is done by consulting the <code>Group</code> of labels
 * representing the union declaration.
 *
 * @author Niall Gallagher
 */
class CompositeListUnion implements Repeater {

    /**
     * This contains the labels in the union group keyed by name.
     */
    private final LabelMap elements;

    /**
     * This is the path expression used to represent this union.
     */
    private final Expression path;

    /**
     * This is the current context used for the serialization.
     */
    private final Context context;

    /**
     * This contains the group of labels associated with the union.
     */
    private final Group group;

    /**
     * This is this style associated with the serialization context.
     */
    private final Style style;

    /**
     * This is the type field or method annotated as a union.
     */
    private final Type type;

    /**
     * Constructor for the <code>CompositeListUnion</code> object. This
     * is used to create a converter that delegates to other associated
     * converters within the union group depending on the XML element
     * name being read or the instance type that is being written.
     *
     * @param context this is the context used for the serialization
     * @param group   this is the union group used for delegation
     * @param path    this is the path expression representing this union
     * @param type    this is the annotated field or method to be used
     */
    public CompositeListUnion(Context context, Group group, Expression path, Type type) throws Exception {
        this.elements = group.getElements();
        this.style = context.getStyle();
        this.context = context;
        this.group = group;
        this.type = type;
        this.path = path;
    }

    /**
     * The <code>read</code> method uses the name of the XML element to
     * select a converter to be used to read the instance. Selection of
     * the converter is done by looking up the associated label from
     * the union group using the element name. Once the converter has
     * been selected it is used to read the instance.
     *
     * @param node this is the XML element used to read the instance
     * @return this is the instance that has been read by this
     */
    public Object read(InputNode node) throws Exception {
        Label text = group.getText();

        if (text == null) {
            return readElement(node);
        }
        return readText(node);
    }

    /**
     * The <code>readElement</code> method uses the name of the element
     * to select a converter to be used to read the instance. Selection
     * of the converter is done by looking up the associated label from
     * the union group using the element name. Once the converter has
     * been selected it is used to read the instance.
     *
     * @param node this is the XML element used to read the instance
     * @return this is the instance that has been read by this
     */
    private Object readElement(InputNode node) throws Exception {
        String    name      = node.getName();
        String    element   = path.getElement(name);
        Label     label     = elements.get(element);
        Converter converter = label.getConverter(context);

        return converter.read(node);
    }

    /**
     * The <code>readText</code> method is used to read free text from
     * between the declared elements and add them to a list. Consuming
     * free text in this manner enables an element list union to parse
     * unstructured XML such as XHTML.
     *
     * @param node this is the node to consume the free text from
     * @return this returns the list with the text added to it
     */
    private Object readText(InputNode node) throws Exception {
        Label     text      = group.getText();
        Converter converter = text.getConverter(context);

        return converter.read(node);
    }

    /**
     * The <code>read</code> method uses the name of the XML element to
     * select a converter to be used to read the instance. Selection
     * of the converter is done by looking up the associated label from
     * the union group using the element name. Once the converter has
     * been selected it is used to read the instance.
     *
     * @param node  this is the XML element used to read the instance
     * @param value this is the value that is to be repeated
     * @return this is the instance that has been read by this
     */
    public Object read(InputNode node, Object value) throws Exception {
        Object result = readElement(node, value);
        Label  text   = group.getText();

        if (text != null) {
            return readText(node, value);
        }
        return result;
    }

    /**
     * The <code>readElement</code> method uses the name of the element
     * to select a converter to be used to read the instance. Selection
     * of the converter is done by looking up the associated label from
     * the union group using the element name. Once the converter has
     * been selected it is used to read the instance.
     *
     * @param node  this is the XML element used to read the instance
     * @param value this is the value that is to be repeated
     * @return this is the instance that has been read by this
     */
    private Object readElement(InputNode node, Object value) throws Exception {
        String    name      = node.getName();
        String    element   = path.getElement(name);
        Label     label     = elements.get(element);
        Converter converter = label.getConverter(context);

        return converter.read(node, value);
    }

    /**
     * The <code>readText</code> method is used to read free text from
     * between the declared elements and add them to a list. Consuming
     * free text in this manner enables an element list union to parse
     * unstructured XML such as XHTML.
     *
     * @param node  this is the node to consume the free text from
     * @param value this is the value that is to be repeated
     * @return this returns the list with the text added to it
     */
    private Object readText(InputNode node, Object value) throws Exception {
        Label     label     = group.getText();
        Converter converter = label.getConverter(context);
        InputNode parent    = node.getParent();

        return converter.read(parent, value);
    }

    /**
     * The <code>validate</code> method is used to validate the XML
     * element provided using an associated class schema. The schema
     * is selected using the name of the XML element to acquire
     * the associated converter. Once the converter has been acquired
     * it is delegated to and validated against it.
     *
     * @param node this is the input XML element to be validated
     * @return this returns true if the node validates
     */
    public boolean validate(InputNode node) throws Exception {
        String    name      = node.getName();
        String    element   = path.getElement(name);
        Label     label     = elements.get(element);
        Converter converter = label.getConverter(context);

        return converter.validate(node);
    }

    /**
     * The <code>write</code> method uses the name of the XML element to
     * select a converter to be used to write the instance. Selection of
     * the converter is done by looking up the associated label from
     * the union group using the instance type. Once the converter has
     * been selected it is used to write the instance.
     *
     * @param source this is the source collection to be serialized
     * @param node   this is the XML element container to be populated
     */
    public void write(OutputNode node, Object source) throws Exception {
        Collection list = (Collection) source;

        if (group.isInline()) {
            if (!list.isEmpty()) {
                write(node, list);
            } else if (!node.isCommitted()) {
                node.remove();
            }
        } else {
            write(node, list);
        }
    }

    /**
     * The <code>write</code> method uses the name of the XML element to
     * select a converter to be used to write the instance. Selection of
     * the converter is done by looking up the associated label from
     * the union group using the instance type. Once the converter has
     * been selected it is used to write the instance.
     *
     * @param node this is the XML element used to write the instance
     * @param list this is the value that is to be written
     */
    private void write(OutputNode node, Collection list) throws Exception {
        for (Object item : list) {
            if (item != null) {
                Class real  = item.getClass();
                Label label = group.getLabel(real);

                if (label == null) {
                    throw new UnionException(node, "Entry of %s not declared in %s with annotation %s", real, type, group);
                }
                write(node, item, label);
            }
        }
    }

    /**
     * The <code>write</code> method uses the name of the XML element to
     * select a converter to be used to write the instance. Selection of
     * the converter is done by looking up the associated label from
     * the union group using the instance type. Once the converter has
     * been selected it is used to write the instance.
     *
     * @param node  this is the XML element used to write the instance
     * @param item  this is the individual list entry to be serialized
     * @param label this is the label to used to acquire the converter
     */
    private void write(OutputNode node, Object item, Label label) throws Exception {
        Converter  converter = label.getConverter(context);
        Collection list      = Collections.singleton(item);

        if (!label.isInline()) {
            String name = label.getName();
            String root = style.getElement(name);

            if (!node.isCommitted()) {
                node.setName(root);
            }
        }
        converter.write(node, list);
    }
}
